﻿namespace EasyToolkit.Excel.BindingTemplates.Definitions
{
    using System;
    using System.Collections.Generic;
    using System.Reflection;
    using EasyToolkit.BindingTemplates.Definitions;
    using EasyToolkit.BindingTemplates.Definitions.Binding;
    using EasyToolkit.Excel.BindingTemplates.Controls.Button;
    using EasyToolkit.Excel.BindingTemplates.Controls.CheckBox;
    using EasyToolkit.Excel.BindingTemplates.Controls.FormulaResult;
    using EasyToolkit.Excel.BindingTemplates.Controls.NamedRange;
    using EasyToolkit.Excel.ContextualMenus;
    using EasyToolkit.Tools.Extensions;
    using Microsoft.Office.Interop.Excel;

    class ExcelTemplateDefinitionPart : TemplateDefinition
    {
        public const string LINKED_TEMPLATE_PREFIX = "<Link";

        #region attributes and properties
        protected Range templateDeclarationFirstRange;
        protected Range templateDeclarationLastRange;

        public Range TemplateFirstCell
        { get; protected set; }

        public Range TemplateCells
        { get; protected set; }

        public int Width
        { get; protected set; }

        public int Height
        { get; protected set; }

        public IDefinitionPart[,] TemplatesParts
        { get; protected set; }

        public List<List<int>> PositionLinkedTemplates
        { get; protected set; }

        public IContextualMenu ContextualMenu
        { get; protected set; }

        public MethodInfo SelectionChanged
        { get; protected set; }
        #endregion
        
        #region .ctors
        public ExcelTemplateDefinitionPart(string name, TemplateOption templateOption)
                                          : base(name, templateOption)
        {
            PositionLinkedTemplates = new List<List<int>>();
        }

        public ExcelTemplateDefinitionPart(string name, TemplateDefinition parent)
                                          : base(name, parent)
        {
            PositionLinkedTemplates = new List<List<int>>();
        }
        #endregion

        #region private methods
        /// <summary> Parse the template. Retrieve its components. </summary>
        protected void ParsePartTemplate(Range templateDeclarationFirstRange, Range templateDeclarationLastRange)
        {
            this.templateDeclarationFirstRange = templateDeclarationFirstRange;
            this.templateDeclarationLastRange = templateDeclarationLastRange;

            Width = templateDeclarationLastRange.Column - templateDeclarationFirstRange.Column + 1;
            Height = templateDeclarationLastRange.Row - templateDeclarationFirstRange.Row + 1;
            Range templateRange = templateDeclarationFirstRange;
            TemplateCells = TemplateFirstCell = templateRange.Resize[1, 1];

            TemplateCells = templateRange.Resize[Height, Width];
            TemplatesParts = new IDefinitionPart[Height, Width];
            AnalyzePart();
        }

        private void AnalyzePart()
        {
            for (int rowId = 0; rowId < TemplateCells.Rows.Count; rowId++)
            {
                List<int> posLinks = null;
                Range row = TemplateCells.Rows[rowId + 1];

                for (int cellId = 0; cellId < row.Cells.Count; cellId++)
                {
                    Range cell = row.Cells[cellId + 1];
                    IDefinitionPart part = AnalyzeCell(cell);
                    TemplatesParts[rowId, cellId] = part;

                    if (part is LinkedTemplateDefinition)
                    {
                        if (posLinks == null)
                            posLinks = new List<int>();
                        posLinks.Add(cellId);
                    }
                }
                PositionLinkedTemplates.Add(posLinks);
            }            
        }

        private IDefinitionPart AnalyzeCell(Range cell)
        {
            IDefinitionPart part = null;
            if(cell.Value2 != null)
            {
                string value = cell.Value2.ToString();
                if (!string.IsNullOrEmpty(value))
                {
                    string trimmedValue = value.Trim();
                    if (trimmedValue.StartsWith(LINKED_TEMPLATE_PREFIX))
                    {
                        try
                        {
                            TemplateLink templateLink = trimmedValue.Deserialize<TemplateLink>();
                            templateLink.To = templateLink.To.EmptyIfNull().Trim();
                            templateLink.With = templateLink.With.EmptyIfNull().Trim();
                            if (string.IsNullOrEmpty(templateLink.To))
                                throw new EasyToolkitException("Attribut 'To' cannot be null or empty", false);
                            if (string.IsNullOrEmpty(templateLink.With))
                                throw new EasyToolkitException("Attribut 'With' cannot be null or empty", false);
                            ExcelTemplateDefinition templateDefinition = (ExcelBindingTemplateManager.Instance as ExcelBindingTemplateManager).GetTemplateDefinitionFromLink(this, templateLink);
                            LinkedTemplateDefinition linkedTemplateDefinition = new LinkedTemplateDefinition(this, templateDefinition, templateLink);
                            this.AddLinkedTemplate(linkedTemplateDefinition);
                            part = linkedTemplateDefinition;
                        }
                        catch (Exception ex)
                        {
                            string message = string.Format("Cannot create the linked template definition '{0}'. {1}", trimmedValue, ex.Message);
                            throw new EasyToolkitException(message, false);
                        }
                    }
                    else
                    {
                        try
                        {
                            IBindingDefinition bindingDefinition = CreateBindingDefinition(value, trimmedValue);
                            this.AddBindingDefinition(bindingDefinition);
                            part = bindingDefinition;
                        }
                        catch (Exception ex)
                        {
                            string message = string.Format("Cannot create the binding definition for '{0}'. {1}", trimmedValue, ex.Message);
                            throw new EasyToolkitException(message, false);
                        }
                    }
                }
            }
            return part;
        }

        private IBindingDefinition CreateBindingDefinition(string value, string trimmedValue)
        {
            IBindingDefinition ret = null;
            if (trimmedValue.StartsWith(ExcelBindingDefinitionButton.BUTTON_TEMPLATE_PREFIX))
                ret = ExcelBindingDefinitionButton.CreateInstance(this, trimmedValue);
            else if (trimmedValue.StartsWith(ExcelBindingDefinitionCheckBox.CHECKBOX_TEMPLATE_PREFIX))
                ret = ExcelBindingDefinitionCheckBox.CreateInstance(this, trimmedValue);
            else if (trimmedValue.StartsWith(ExcelBindingDefinitionFormulaResult.FORMULA_RESULT_PREFIX))
                ret = ExcelBindingDefinitionFormulaResult.CreateInstance(this, trimmedValue);
            else if (trimmedValue.StartsWith(ExcelBindingDefinitionNamedRange.NAMEDRANGE_TEMPLATE_PREFIX))
            {
                ExcelNamedRangeDefinition definition = ExcelBindingDefinitionNamedRange.RetrieveNamedRangeDefinition(trimmedValue);
                if (definition != null)
                {
                    IBindingDefinition nestedBindingDefinition = null;
                    if (! string.IsNullOrEmpty(definition.Value))
                        nestedBindingDefinition = CreateBindingDefinition(definition.Value, definition.Value.Trim());
                    ret = ExcelBindingDefinitionNamedRange.CreateInstance(this, definition, nestedBindingDefinition);
                }
            }
            else 
            {
                BindingDefinitionDescription bindingDefinitionDescription = BindingDefinitionDescription.CreateBindingDescription(value, trimmedValue);
                ret = BindingDefinitionFactory.CreateCommonBindingDefinition(this, bindingDefinitionDescription);
            }
            return ret;
        }
        #endregion
    }
}
